﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using NUnit.Framework;

namespace spike_line_index_stream
{
    public class StreamLineIndexEnumerator
    {
    }


    [TestFixture]
    public class test_StreamLineIndexEnumerator
    {
        [Test]
        public void Find_index_of_first_line()
        {
            Assert.AreEqual(0, GetIndexOfFirstLine("TestAscii.txt"));
            Assert.AreEqual(3, GetIndexOfFirstLine("TestUtf-8.txt"));
            Assert.AreEqual(2, GetIndexOfFirstLine("TestUnicode.txt"));

            Assert.AreEqual(-1, GetIndexOfFirstLine("Test0Bytes.txt"));
            Assert.AreEqual(0, GetIndexOfFirstLine("TestBinaryFile.txt"));
            Assert.AreEqual(2, GetIndexOfFirstLine("TestUnicodeNoText.txt"));
        }


        [Test]
        public void Find_indexes_of_lines()
        {
            Assert.AreEqual(new long[] { 0, 3, 7, 12 }, FindLineIndexes("TestAscii.txt", 0).ToArray());
            Assert.AreEqual(new long[] { 3, 6, 11, 17 }, FindLineIndexes("TestUtf-8.txt", 3).ToArray());
        }


        private IEnumerable<long> FindLineIndexes(string filename, long indexOfFirstLine)
        {
            using (var f = new FileStream(filename, FileMode.Open, FileAccess.Read))
            {
                // Position on first line
                f.Seek(indexOfFirstLine, SeekOrigin.Begin);
                yield return f.Position;

                // Read all bytes...
                int b;
                while ((b = f.ReadByte()) >= 0)
                {
                    // If CR found...
                    if ((char)b == '\r')
                    {
                        //...check for following LF
                        b = f.ReadByte();
                        if (b < 0)
                            break;
                        if ((char)b == '\n')
                            // LF found, i.e. a new line is beginning after it
                            yield return f.Position;
                    }

                }
            }
        }


        private int GetIndexOfFirstLine(string filename)
        {
            var byteOrderMarks = new List<byte[]>
                                     {
                                         new byte[]{239, 187, 191}, // UTF-8
                                         new byte[]{254, 255}, // UTF-16 (BE)
                                         new byte[]{255, 254} // UTF-16 (LE)
                                     };

            using (var f = new FileStream(filename, FileMode.Open))
            {
                // No line index for empty file
                if (f.Length == 0)
                    return -1;

                // For each byte check all byte order mark candidates.
                // Remove non-matching byte order mark candidates from list for following bytes.
                while (byteOrderMarks.Count > 0)
                {
                    var b = f.ReadByte(); 
                    
                    // File shorter than remaining byte order marks
                    if (b < 0) 
                        break;

                    // Match byte order mark and weed out non-matching
                    for (var i = byteOrderMarks.Count - 1; i >= 0; i--)
                        if (b == byteOrderMarks[i][f.Position - 1])
                        {
                            // Byte order mark found
                            if (f.Position == byteOrderMarks[i].Length)
                                return byteOrderMarks[i].Length;
                        }
                        else
                            // Exclude byte order mark from further checking
                            byteOrderMarks.RemoveAt(i);
                }

                // No byte order marks found
                return 0;
            }
        }
    }  

}
